#include "stdafx.h"
#include "HXDirector.h"
#include "HXLink.h"
#include "HXPlayer.h"

//=============================================================================

HXLink g_link; // Synapse interface

//=============================================================================

//! Constructor.
HXLink::HXLink()
: m_hThread(0),
  m_bThreadExit(false),
  m_bCreatedSyns(false),
  m_backgroundStatus(HXDYB_NONE),
  m_headNumber(0),
  m_pCameraLocalBuffer(NULL),
  m_pCameraSynapseBuffer(NULL),
  m_pMorphLocalBuffer(NULL),
  m_pMorphSynapseBuffer(NULL)
{
}

//! Destructor.
HXLink::~HXLink()
{
	Disconnect();
}

//! Create Synapses and start polling thread.
bool HXLink::Connect()
{
	Disconnect();
	if (m_bCreatedSyns = CreateSynapses())
	{
		m_hThread = CreateThread( 
			             NULL,       // default security attributes
				         0,          // default stack size
						 (LPTHREAD_START_ROUTINE) RunLinkThread, 
						 this,		 // object pointer
						0,	         // default creation flags
						NULL);		 // no thread identifier
		return true;
	}
	return false;
}

//! Close Synapses and stop polling thread.
void HXLink::Disconnect()
{
	if (m_hThread)
	{
		m_bThreadExit = true;
		WaitForSingleObject(m_hThread, INFINITE);
		CloseHandle(m_hThread);
		m_bThreadExit = false;
		m_hThread = 0;
	}
	if (m_bCreatedSyns)
		ReleaseSynapses();
}

//***********************

//! Check Synapses for new data.
void HXLink::CheckSynapses()
{
	// COMMAND Synapse
	if (g_config.bSynapseCommands && m_sTextCommand.LockNow())
	{
		if (m_sTextCommand.IsNew())
		{
			m_linkBuffer[0] = 0;
			m_sTextCommand.ReadArray(m_linkBuffer);
			if (m_linkBuffer[0])
			{
				m_sHeadStatus.Lock(); // can cause deadlocks if other thread also locks both synapses simultaneously
				long& ts = m_sHeadStatus.Grab(1); // set speech status to active (if not already speaking)
				if (ts == HXSY_NONE)
				{
					ts = HXSY_ACTIVE;
				}
				m_sHeadStatus.Unlock();
				AsyncSpeak(StringToWString(string(m_linkBuffer)), g_config.bBlockSynapse);
				m_linkBuffer[0] = 0;
			}
		}
		m_sTextCommand.Unlock();
	}

	// DIRECTCOMMAND Synapse
	if (g_config.bSynapseDirect && m_sDirectCommand.LockNow())
	{
		if (m_sDirectCommand.IsNew())
		{
			m_linkBuffer[0] = 0;
			m_sDirectCommand.ReadArray(m_linkBuffer);
			if (m_linkBuffer[0])
			{
				g_director.AsyncExecute(StringToWString(string(m_linkBuffer)).c_str());
			}
		}
		m_sDirectCommand.Unlock();
	}

	// CAMERA Synapse
	if (g_config.bSynapseCamera)
	{
		if (!m_pCameraLocalBuffer)
		{
			m_pCameraLocalBuffer = new float[m_sCamera.Size()];
			m_pCameraSynapseBuffer = new float[m_sCamera.Size()];
			for (int i = 0; i < m_sCamera.Size(); i++)
			{
				m_pCameraLocalBuffer[i] = 0.0f;
			}
			g_director.GetDisplay()->ReadCameraLights(m_pCameraLocalBuffer);
		}
		if (m_sCamera.IsNew()) // someone has written to the Synapse?
		{
			m_sCamera.ReadArray(m_pCameraSynapseBuffer);
			// calculate difference between synapse content and buffer
			for (int i = 0; i < m_sCamera.Size(); i++)
			{
				m_pCameraSynapseBuffer[i] -= m_pCameraLocalBuffer[i];
			}
			// apply difference to display
			g_director.GetDisplay()->ChangeCameraLights(m_pCameraSynapseBuffer);
		}
		if (m_sCamera.LockNow())
		{
			if (m_sCamera.SharedInfo().currentReaders == 0)
			{
				g_director.GetDisplay()->ReadCameraLights(m_pCameraLocalBuffer);	
				m_sCamera.FillPointer(m_pCameraLocalBuffer);
				m_sCamera.SetAsWritten();
				m_sCamera.LocalInfo().lastReadTime = m_sCamera.Time();
			}
			m_sCamera.Unlock();
		}
	}
	else if (m_pCameraLocalBuffer)
	{
		delete [] m_pCameraLocalBuffer;
		delete [] m_pCameraSynapseBuffer;
		m_pCameraLocalBuffer = NULL;
		m_pCameraSynapseBuffer = NULL;
	}

	// HEADMORPHS Synapse
	if (g_config.bSynapseFaceMorphs)
	{
		if (!m_pMorphLocalBuffer)
		{
			m_pMorphLocalBuffer = new float[m_sHeadMorphs.Size()];
			m_pMorphSynapseBuffer = new float[m_sHeadMorphs.Size()];
			for (int i = 0; i < m_sHeadMorphs.Size(); i++)
			{
				m_pMorphLocalBuffer[i] = 0.0f;
			}
			g_director.GetDisplay()->GetRemoteMorphs(m_pMorphLocalBuffer, MAX_SYNAPSE_MORPHS);
		}
		if (m_sHeadMorphs.IsNew())
		{
			m_sHeadMorphs.ReadArray(m_pMorphSynapseBuffer);
			for (int i = 0; i < m_sHeadMorphs.Size(); i++)
			{
				m_pMorphSynapseBuffer[i] -= m_pMorphLocalBuffer[i];
			}
			g_director.GetDisplay()->ChangeRemoteMorphs(m_pMorphSynapseBuffer, MAX_SYNAPSE_MORPHS);
		}
		if (m_sHeadMorphs.LockNow())
		{
			if (m_sHeadMorphs.SharedInfo().currentReaders == 0)
			{
				g_director.GetDisplay()->GetRemoteMorphs(m_pMorphLocalBuffer, MAX_SYNAPSE_MORPHS);
				m_sHeadMorphs.FillPointer(m_pMorphLocalBuffer);
				m_sHeadMorphs.SetAsWritten();
				m_sHeadMorphs.LocalInfo().lastReadTime = m_sHeadMorphs.Time();
			}
			m_sHeadMorphs.Unlock();
		}
	}
	else if (m_pMorphLocalBuffer)
	{
		g_director.GetDisplay()->ResetRemoteMorphs();
		delete [] m_pMorphLocalBuffer;
		delete [] m_pMorphSynapseBuffer;
		m_pMorphLocalBuffer = NULL;
		m_pMorphSynapseBuffer = NULL;
	}
}

bool HXLink::OpenBackground(string name, int width, int height)
{
	m_backgroundStatus = HXDYB_NONE;
	m_sBackground.Release();
	if (name.size())
	{
		if ((m_sBackground.OpenNow(name.c_str()) || m_sBackground.Create(name.c_str(), width * height)) && (m_sBackground.Size() == width * height))
		{
			m_backgroundStatus = HXDYB_NEW;
		}
		else
		{
			m_sBackground.Release();
		}
	}
	return (m_backgroundStatus != HXDYB_NONE);
}

bool HXLink::IsBackgroundModified()
{
	return m_sBackground.IsNew();
}

HXRGBA* HXLink::GrabBackground()
{
	m_sBackground.Lock();
	return static_cast<HXRGBA*>(m_sBackground.GrabPointer());
}

void HXLink::DoneWithBackground()
{
	m_sBackground.Unlock();
}

//! Get a WString describing the currently implemented Synapses and their states.
wstring HXLink::GetCapabilityString()
{
	wchar_t hn[20];
	swprintf_s(hn, 20, L"Head-X(%u)", GetHeadNumber());
	wchar_t capbuf[1024];
	swprintf_s(capbuf, 1024, L"%s\n%s (%d)\n%s%s (%d)\n%s%s%u] (%s)\n%s%s%u] (%s)\n%s%s%u]\n%s%s%u] (%s)\n%s%s%u] (%s)\n%s%s%u]\n%s%s%u]\n%s%s%u]\n%s%s%u]",
							L"Synapse Name [type, array-size] (content: first 100 characters):\n",
							L"Head-X [int32, 1]", GetInstances(),
							hn, L" [int32, 1]", GetVersion(),
							hn, L"-WorkingPath [char, ", m_sWorkingPath.Size(), GetWorkingPath().substr(0, 100).c_str(),
							hn, L"-Command *[char, ", m_sTextCommand.Size(), GetCommand().substr(0, 100).c_str(),
							hn, L"-DirectCommand *[char, ", m_sDirectCommand.Size(),
							hn, L"-UserText [char, ", m_sUserText.Size(), GetUserText().substr(0, 100).c_str(),
							hn, L"-SpokenText [char, ", m_sSpokenText.Size(), GetSpokenText().substr(0, 100).c_str(),
							hn, L"-Status [int32, ", m_sHeadStatus.Size(),
							hn, L"-KBM [int32, ", m_sKBM.Size(),
							hn, L"-HeadMorphs *[float, ", m_sHeadMorphs.Size(),
							hn, L"-Camera *[float, ", m_sCamera.Size()
							);
	return wstring(capbuf);
}

//***********************

DWORD WINAPI HXLink::RunLinkThread(LPVOID lpParam) 
{ 
	return ((HXLink*)lpParam)->LinkThread();
}

DWORD WINAPI HXLink::LinkThread()
{
	HXStopWatch linkClock;
	while (!m_bThreadExit) 
	{
		linkClock.startTimer();
		CheckSynapses();
		linkClock.stopTimer();
		int processTime = static_cast<int>(linkClock.getElapsedTime() * 1000);
		if (processTime < g_director.GetFrameTime())
		{
			Sleep(g_director.GetFrameTime() - processTime);
		}
	}
	return 0;
}

//***********************

bool HXLink::CreateSynapses()
{
	char synameBuffer[100];
	m_headNumber = 0;
	while (true)
	{
		sprintf_s(synameBuffer, 100, "Head-X(%u)", ++m_headNumber);
		if (m_sVersion.OpenNow(synameBuffer))
		{	
			if (!GetVersion())
			{
				break;
			}
			m_sVersion.Release();
		}
		else
		{
			break;
		}
	}
	if (m_sVersion.IsOpen() || m_sVersion.Create(synameBuffer, 1))
	{
		m_sVersion.Write(HEADX_VERSION);
		if (m_sInstances.OpenNow("Head-X") || m_sInstances.Create("Head-X"))
		{
			m_sInstances.Lock();
			m_sInstances.Grab() += 1;
			m_sInstances.Unlock();
			char sytargetBuffer[100];
			sprintf_s(sytargetBuffer, 100, "%s-Command", synameBuffer);
			if (m_sTextCommand.OpenNow(sytargetBuffer) || m_sTextCommand.Create(sytargetBuffer, MAX_TEXT_LENGTH))
			{
				m_sTextCommand.SetAsRead();
				sprintf_s(sytargetBuffer, 100, "%s-SpokenText", synameBuffer);
				if ((m_sTextCommand.Size() >= MAX_TEXT_LENGTH) && (m_sSpokenText.OpenNow(sytargetBuffer) || m_sSpokenText.Create(sytargetBuffer, MAX_TEXT_LENGTH)))
				{
					sprintf_s(sytargetBuffer, 100, "%s-UserText", synameBuffer);
					if ((m_sSpokenText.Size() >= MAX_TEXT_LENGTH) && (m_sUserText.OpenNow(sytargetBuffer) || m_sUserText.Create(sytargetBuffer, MAX_TEXT_LENGTH)))
					{
						sprintf_s(sytargetBuffer, 100, "%s-Status", synameBuffer);
						if ((m_sUserText.Size() >= MAX_TEXT_LENGTH) && (m_sHeadStatus.OpenNow(sytargetBuffer) || m_sHeadStatus.Create(sytargetBuffer, 5)))
						{
							SetWordIndex(-1);
							sprintf_s(sytargetBuffer, 100, "%s-Camera", synameBuffer);
							if (m_sCamera.OpenNow(sytargetBuffer) || m_sCamera.Create(sytargetBuffer, 30)) 
							{
								sprintf_s(sytargetBuffer, 100, "%s-HeadMorphs", synameBuffer);
								if (m_sHeadMorphs.OpenNow(sytargetBuffer) || m_sHeadMorphs.Create(sytargetBuffer, MAX_SYNAPSE_MORPHS)) 
								{
									sprintf_s(sytargetBuffer, 100, "%s-KBM", synameBuffer);
									if (m_sKBM.OpenNow(sytargetBuffer) || m_sKBM.Create(sytargetBuffer, 6))
									{
										sprintf_s(sytargetBuffer, 100, "%s-WorkingPath", synameBuffer);
										if (m_sWorkingPath.OpenNow(sytargetBuffer) || m_sWorkingPath.Create(sytargetBuffer, MAX_PATH))
										{
											sprintf_s(sytargetBuffer, 100, "%s-DirectCommand", synameBuffer);
											if (m_sDirectCommand.OpenNow(sytargetBuffer) || m_sDirectCommand.Create(sytargetBuffer, MAX_TEXT_LENGTH))
											{
												if (m_sDirectCommand.Size() >= MAX_TEXT_LENGTH)
												{
													return true;
												}
											}
											m_sDirectCommand.Release();
										}
										m_sWorkingPath.Release();
									}
									m_sKBM.Release();
								}
								m_sHeadMorphs.Release();
							}
							m_sCamera.Release();
						}
						m_sHeadStatus.Release();
					}
					m_sUserText.Release();
				}
				m_sSpokenText.Release();
			}
			m_sTextCommand.Release();
		}
		m_sInstances.Lock();
		m_sInstances.Grab() -= 1;
		m_sInstances.Unlock();
		m_sInstances.Release();
	}
	m_sVersion.Release();
	return false;
}

void HXLink::ReleaseSynapses()
{
	m_bCreatedSyns = false;
	m_backgroundStatus = HXDYB_NONE;
	m_sBackground.Release();
	m_sDirectCommand.Release();
	m_sWorkingPath.Release();
	m_sKBM.Release();
	m_sCamera.Release();
	m_sHeadStatus.Release();
	m_sUserText.Release();
	m_sSpokenText.Release();
	m_sTextCommand.Release();
	m_sInstances.Release();
	m_sVersion.Release();
	delete [] m_pCameraLocalBuffer;
	delete [] m_pCameraSynapseBuffer;
	m_pCameraLocalBuffer = NULL;
	m_pCameraSynapseBuffer = NULL;
	delete [] m_pMorphLocalBuffer;
	delete [] m_pMorphSynapseBuffer;
	m_pMorphLocalBuffer = NULL;
	m_pMorphSynapseBuffer = NULL;
}
